package server

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
)

type Server struct {
	logger                             *logrus.Logger
	port                               string
	taskHandler                        map[string]TaskHandler
	engine                             *gin.Engine
	taskChan                           chan *handleTask
	notifyErrorChan                    chan *reqResponse
	notifyGoroutineNum                 int
	maxWaitSecondsWhenNotifyErrorBlock int
	notifyErrorFrequencySeconds        int
	basePath                           string
	retryNotifyTimes                   int
	retryNotifyTimesRecord             map[string]int
	globalErrorHandler                 func(reqId string, taskPath string, paniced interface{}) error
}

type Option func(s *Server)

func WithPort(port string) Option {
	return func(s *Server) {
		s.port = port
	}
}

func WithBasePath(basePath string) Option {
	return func(s *Server) {
		s.basePath = basePath
	}
}

func WithEngine(engine *gin.Engine) Option {
	return func(s *Server) {
		s.engine = engine
	}
}

func WithLogger(logger *logrus.Logger) Option {
	return func(s *Server) {
		s.logger = logger
	}
}

// 通知client的goroutine数量
func WithNotifyGoroutineNum(n int) Option {
	return func(s *Server) {
		s.notifyGoroutineNum = n
	}
}

// 通知出错时，需要重新通知。如果队列阻塞时，最长等待时间。
func WithMaxWaitSecondsWhenNotifyErrorBlock(n int) Option {
	return func(s *Server) {
		s.maxWaitSecondsWhenNotifyErrorBlock = n
	}
}

// 通知出错时，需要重新通知。每n秒，重新通知一次。
func WithNotifyErrorFrequencySeconds(n int) Option {
	return func(s *Server) {
		s.notifyErrorFrequencySeconds = n
	}
}

// 通知出错时，最多尝试次数
func WithRetryNotifyTimes(n int) Option {
	return func(s *Server) {
		s.retryNotifyTimes = n
	}
}

// 全局错误处理。如果ef返回的error不为nil，则会将error信息通知到client
func WithGlobalErrorHandler(ef func(reqId string, taskPath string, paniced interface{}) error) Option {
	return func(s *Server) {
		s.globalErrorHandler = ef
	}
}

func NewServer(options ...Option) *Server {
	s := &Server{
		logger:                             &logrus.Logger{Out: os.Stderr, Formatter: new(logrus.TextFormatter), Hooks: make(logrus.LevelHooks), Level: logrus.InfoLevel},
		port:                               "9000",
		taskHandler:                        map[string]TaskHandler{},
		engine:                             nil,
		taskChan:                           make(chan *handleTask),
		notifyErrorChan:                    make(chan *reqResponse, 128),
		notifyGoroutineNum:                 5,
		maxWaitSecondsWhenNotifyErrorBlock: 5,
		notifyErrorFrequencySeconds:        5,
		basePath:                           "/task",
		retryNotifyTimes:                   5,
		retryNotifyTimesRecord:             map[string]int{},
	}
	for _, option := range options {
		option(s)
	}
	return s
}

func (s *Server) Close() {
	close(s.notifyErrorChan)
	close(s.taskChan)
}

type reqResponse struct {
	reqId     string
	result    string
	msg       string
	response  string
	clientUrl string
}

func (s *Server) RegisterTaskHandler(taskPath string, handler TaskHandler) {
	s.taskHandler[taskPath] = handler
}

func (s *Server) asynHandlerFun(ctx *gin.Context) {
	ctx.Request.ParseForm()
	taskPath := ctx.Param("taskPath")
	reqId := ctx.Request.FormValue("reqId")

	if handler, ok := s.taskHandler[taskPath]; ok {
		clientUrl := ctx.Request.FormValue("clientUrl")
		params := make(map[string]string)

		for k, vs := range ctx.Request.Form {
			if len(vs) > 0 {
				params[k] = vs[0]
			}
		}
		for k, vs := range ctx.Request.PostForm {
			if len(vs) > 0 {
				params[k] = vs[0]
			}
		}
		s.logger.Debugf("AsynHandlerFun,params: %s", params)

		task := &handleTask{
			handler:   handler,
			params:    params,
			reqId:     reqId,
			clientUrl: clientUrl,
			taskPath:  taskPath,
		}
		s.taskChan <- task
		ctx.JSON(200, gin.H{
			"result": "success",
		})

	} else {
		msg := "can not find handler by uri: " + taskPath + ",reqId: " + reqId
		s.logger.Error(msg)
		ctx.JSON(200, gin.H{
			"result": "fail",
			"msg":    msg,
		})
	}

}

type handleTask struct {
	handler   TaskHandler
	params    map[string]string
	reqId     string
	clientUrl string
	taskPath  string
}

func (s *Server) Start() {

	s.startNotify()

	newEngine := false
	r := s.engine
	if r == nil {
		r = gin.Default()
		newEngine = true
	}

	r.POST(s.basePath+"/*taskPath", func(ctx *gin.Context) {
		s.asynHandlerFun(ctx)
	})
	if newEngine {
		r.Run(":" + s.port)
	}

}

func (s *Server) startNotify() {
	for i := 0; i < s.notifyGoroutineNum; i++ {
		go s.notify()
	}
	go func() {
		for res := range s.notifyErrorChan {
			if s.retryNotifyTimesRecord[res.reqId] >= s.retryNotifyTimes {
				delete(s.retryNotifyTimesRecord, res.reqId)
				s.logger.Infof("retry notify exceeded the prescribed number of times:%d, discard. reqId:%s, clientUrl:%v, result:%s",
					s.retryNotifyTimes, res.reqId, res.clientUrl, res.result)
				continue
			}
			s.retryNotifyTimesRecord[res.reqId]++
			s.logger.Debugf("begin retry notify client. reqId:%s, clientUrl:%v, result:%s", res.reqId, res.clientUrl, res.result)
			err := s.notifyClient(res)
			if err != nil {
				s.retryNotify(res)
			} else {
				delete(s.retryNotifyTimesRecord, res.reqId)
			}
			time.Sleep(time.Duration(s.notifyErrorFrequencySeconds) * time.Second)
		}
	}()

}

func (s *Server) notify() {
	for task := range s.taskChan {
		response, err := s.handleTask(task)
		if err != nil {
			s.logger.Errorf("Handle task error, reqId:%s, taskPath:%s,params:%s, err:%v",
				task.reqId, task.taskPath, task.params, err)
		}
		if len(task.clientUrl) == 0 {
			continue
		}
		result := "success"
		msg := ""
		if err != nil {
			result = "fail"
			msg = err.Error()
		}

		res := &reqResponse{
			reqId:     task.reqId,
			result:    result,
			msg:       msg,
			response:  response,
			clientUrl: task.clientUrl,
		}
		err = s.notifyClient(res)
		if err != nil {
			s.retryNotify(res)
		}
	}
}

func (s *Server) handleTask(task *handleTask) (response string, err error) {
	if s.globalErrorHandler != nil {
		defer func() {
			if paniced := recover(); paniced != nil {
				err = s.globalErrorHandler(task.reqId, task.taskPath, paniced)
			}
		}()
	}

	response, err = task.handler.Handle(task.reqId, task.params)
	return
}

func (s *Server) retryNotify(res *reqResponse) error {
	select {
	case s.notifyErrorChan <- res:
	case <-time.After(time.Duration(s.maxWaitSecondsWhenNotifyErrorBlock) * time.Second):
		s.logger.Errorf("wait overrun the time, reqId:%s", res.reqId)
		return fmt.Errorf("wait overrun the time, reqId:%s", res.reqId)
	}
	return nil
}

func (s *Server) notifyClient(res *reqResponse) error {
	var vals = url.Values{}
	vals["reqId"] = []string{res.reqId}
	vals["result"] = []string{res.result}
	vals["msg"] = []string{res.msg}
	vals["response"] = []string{res.response}
	s.logger.Debugf("begin notify client. reqId:%s, clientUrl:%v, params:%s", res.reqId, res.clientUrl, vals)
	resp, err := http.PostForm(res.clientUrl, vals)
	if err != nil {
		s.logger.Errorf("notify client error,reqId:%s, errMsg:%v", res.reqId, err)
		return err
	}

	body, _ := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	var notifyResult = &NotifyResult{}
	json.Unmarshal(body, notifyResult)
	if notifyResult.Result == "success" {
		s.logger.Debugf("notify client success. reqId:%s, clientUrl:%v, params:%s", res.reqId, res.clientUrl, vals)
	} else {
		s.logger.Errorf("notify client error,reqId:%s, errMsg:%v", res.reqId, notifyResult.Msg)
		return fmt.Errorf("notify client error,reqId:%s, errMsg:%v", res.reqId, notifyResult.Msg)
	}
	return nil
}

type NotifyResult struct {
	Result string `json:"result"`
	Msg    string `json:"msg"`
}
